package cn.hbads.support.http.client.model;

import cn.hbads.support.error.CommonErrorType;
import cn.hbads.support.error.ErrorType;
import cn.hbads.support.json.JsonUtil;
import cn.hbads.support.log.LogUtil;
import cn.hbads.support.structure.result.ComputeResult;
import cn.hutool.core.collection.CollUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpRequest;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * <b>Http请求配置模型</b>
 *
 * @author yvvb
 * @date 2025/5/4
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class HttpRequestModel {
    // 请求方式
    private HttpMethod httpMethod;

    // 请求路由
    private URI requestUri;

    // 请求query参数，对应@RequestParam
    private Map<String, Object> parameters;

    // 请求头
    private Map<String, String> headers;

    // 请求体
    private Object body;

    public void addParameter(String key, Object value) {
        if (Objects.isNull(parameters)) {
            parameters = new HashMap<>();
        }
        parameters.put(key, value);
    }

    public void addParameters(Map<String, Object> paramMap) {
        if (Objects.isNull(parameters)) {
            parameters = new HashMap<>();
        }
        if (CollUtil.isNotEmpty(paramMap)) {
            parameters.putAll(paramMap);
        }
    }

    public void addHeader(String key, String value) {
        if (Objects.isNull(headers)) {
            headers = new HashMap<>();
        }
        headers.put(key, value);
    }

    public void addHeaderIfAbsent(String key, String value) {
        if (Objects.isNull(headers)) {
            headers = new HashMap<>();
        }
        headers.putIfAbsent(key, value);
    }

    public void addHeaders(Map<String, String> headerMap) {
        if (Objects.isNull(headers)) {
            headers = new HashMap<>();
        }
        if (CollUtil.isNotEmpty(headerMap)) {
            headers.putAll(headerMap);
        }
    }

    public URI buildRequestUri() {
        UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(this.requestUri);
        if (!CollectionUtils.isEmpty(parameters)) {
            for (Map.Entry<String, Object> entry : parameters.entrySet()) {
                uriComponentsBuilder = uriComponentsBuilder.queryParam(entry.getKey(), entry.getValue());
            }
        }
        return uriComponentsBuilder.build(false).toUri();
    }

    public String[] buildRequestHeaderArr() {

        if (CollectionUtils.isEmpty(headers)) {
            return new String[]{};
        }
        String[] headerArr = new String[headers.size() * 2];
        int index = 0;
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            String k = entry.getKey();
            String v = entry.getValue();
            if (StringUtils.isNotBlank(k) && StringUtils.isNotBlank(v)) {
                headerArr[index++] = k;
                headerArr[index++] = v;
            }
        }
        return headerArr;
    }

    public ComputeResult<HttpRequest.BodyPublisher, ErrorType> buildRequestBodyPublisher() {
        try {
            HttpRequest.BodyPublisher bodyPublisher = null;
            if (ObjectUtils.isEmpty(body)) {
                bodyPublisher = HttpRequest.BodyPublishers.noBody();
            } else if (body instanceof String str) {
                bodyPublisher = HttpRequest.BodyPublishers.ofString(str);
                addHeaderIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_XML_VALUE);
            } else if (body instanceof Path path) {
                bodyPublisher = HttpRequest.BodyPublishers.ofFile(path);
                addHeaderIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
            } else if (body instanceof InputStream inputStream) {
                bodyPublisher = HttpRequest.BodyPublishers.ofInputStream(() -> inputStream);
                addHeaderIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
            } else if (body instanceof Supplier streamSupplier) {
                bodyPublisher = HttpRequest.BodyPublishers.ofInputStream(streamSupplier);
                addHeaderIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
            } else if (body instanceof byte[] byteArray) {
                bodyPublisher = HttpRequest.BodyPublishers.ofByteArray(byteArray);
                addHeaderIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
            } else {
                bodyPublisher = trySerializeToJsonBody();
            }
            return ComputeResult.success(bodyPublisher);
        } catch (Exception e) {
            LogUtil.error("Build request body failed! body:{}", body.toString());
            return ComputeResult.fail(CommonErrorType.BUILD_HTTP_REQUEST_BODY_ERROR, e);
        }
    }

    private HttpRequest.BodyPublisher trySerializeToJsonBody() {
        HttpRequest.BodyPublisher bodyPublisher;
        ComputeResult<String, ErrorType> jsonResult = JsonUtil.useGeneric().toJson(this.body);
        if (jsonResult.isSuccess()) {
            String json = jsonResult.get();
            bodyPublisher = HttpRequest.BodyPublishers.ofString(json);
            addHeaderIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        } else {
            bodyPublisher = HttpRequest.BodyPublishers.ofString(new GenericConversionService().convert(body, String.class));
            addHeaderIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_XML_VALUE);
        }
        return bodyPublisher;
    }
}
